// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Linq;
using Mono.Cecil;

namespace Mono.Linker.Steps
{
    public static class RemoveSecurity
    {
        public static void ProcessAssembly(AssemblyDefinition assembly, LinkContext context)
        {
            if (context.Annotations.GetAction(assembly) == AssemblyAction.Link)
            {
                ClearSecurityDeclarations(assembly);
                RemoveCustomAttributesThatAreForSecurity(assembly);

                RemoveCustomAttributesThatAreForSecurity(assembly.MainModule);

                foreach (var type in assembly.MainModule.Types)
                    ProcessType(type);
            }
        }

        static void ProcessType(TypeDefinition type)
        {
            ClearSecurityDeclarations(type);
            RemoveCustomAttributesThatAreForSecurity(type);
            type.HasSecurity = false;

            foreach (var field in type.Fields)
                RemoveCustomAttributesThatAreForSecurity(field);

            foreach (var method in type.Methods)
            {
                ClearSecurityDeclarations(method);
                RemoveCustomAttributesThatAreForSecurity(method);
                method.HasSecurity = false;
            }

            foreach (var nested in type.NestedTypes)
                ProcessType(nested);
        }

        static void ClearSecurityDeclarations(ISecurityDeclarationProvider provider)
        {
            if (provider.HasSecurityDeclarations)
                provider.SecurityDeclarations.Clear();
        }

        /// <summary>
        /// We have to remove some security attributes, otherwise pe verify will complain that a type has HasSecurity = false
        /// </summary>
        /// <param name="provider"></param>
        static void RemoveCustomAttributesThatAreForSecurity(ICustomAttributeProvider provider)
        {
            if (!provider.HasCustomAttributes)
                return;

            var attrsToRemove = provider.CustomAttributes.Where(IsCustomAttributeForSecurity).ToArray();
            foreach (var remove in attrsToRemove)
                provider.CustomAttributes.Remove(remove);
        }

        static bool IsCustomAttributeForSecurity(CustomAttribute attr)
        {
            var attr_type = attr.AttributeType;
            if (attr_type.Namespace == "System.Security")
            {
                switch (attr_type.Name)
                {
                    case "SecurityCriticalAttribute":
                    case "SecuritySafeCriticalAttribute":
                    case "SuppressUnmanagedCodeSecurityAttribute":
                    case "DynamicSecurityMethodAttribute":
                    case "UnverifiableCodeAttribute":
                    case "AllowPartiallyTrustedCallersAttribute":
                    case "SecurityTransparentAttribute":
                    case "SecurityRulesAttribute":
                        return true;
                }
            }

            return false;
        }
    }
}
